package com.usefullc.user.service;

import cn.hutool.core.util.ClassUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.usefullc.common.constants.CacheConstants;
import com.usefullc.common.constants.SystemConstant;
import com.usefullc.common.constants.UserConstants;
import com.usefullc.system.query.BaseQuery;
import com.usefullc.system.entity.Pager;
import com.usefullc.common.enums.RegisterTypeEnum;
import com.usefullc.common.enums.UserGenderEnum;
import com.usefullc.common.enums.UserStatusEnum;
import com.usefullc.common.enums.UserTypeEnum;
import com.usefullc.common.exception.BizException;
import com.usefullc.system.service.AbstractBizService;
import com.usefullc.common.util.BeanUtils;
import com.usefullc.common.util.JsonUtil;
import com.usefullc.common.util.MD5Utils;
import com.usefullc.common.util.http.HttpNewUtils;
import com.usefullc.common.util.http.RequestRes;
import com.usefullc.common.util.http.ResponseRes;
import com.usefullc.system.service.component.SystemConfigComponent;
import com.usefullc.user.dao.UserDao;
import com.usefullc.user.domain.Employee;
import com.usefullc.user.domain.User;
import com.usefullc.user.param.UserRegisterParam;
import com.usefullc.user.query.EmployeeQuery;
import com.usefullc.user.query.UserQuery;
import com.usefullc.user.vo.UserVo;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.criteria.*;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 用户服务类
 *
 * @author: Sampson
 * @date: 2018-12-19 16:05
 */
@Service
public class UserService extends AbstractBizService<User,UserQuery> {

    @Autowired
    private UserDao userDao;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    @Autowired
    private OuterUserService outerUserService;

    @Autowired
    private EmployeeService employeeService;


    @Autowired
    private SystemConfigComponent systemConfigComponent;


    @Override
    public List<Predicate> getPredicates(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder, UserQuery thisQuery) {
        List<Predicate> predicates = new ArrayList<>();

        if (thisQuery.getDeptId() != null) {
            predicates.add(criteriaBuilder.equal(root.get("dept").get("id"), thisQuery.getDeptId()));
        }

        if (StringUtils.isNotEmpty(thisQuery.getBirthdayStart())) {
            Expression<String> endTimeExpression = criteriaBuilder.function("DATE_FORMAT", String.class,
                    root.get("birthday"), criteriaBuilder.literal("%m-%d"));
            predicates.add(criteriaBuilder.greaterThanOrEqualTo(endTimeExpression, thisQuery.getBirthdayStart()));
        }
        if (StringUtils.isNotEmpty(thisQuery.getBirthdayEnd())) {
            Expression<String> endTimeExpression = criteriaBuilder.function("DATE_FORMAT", String.class,
                    root.get("birthday"), criteriaBuilder.literal("%m-%d"));
            predicates.add(criteriaBuilder.lessThanOrEqualTo(endTimeExpression, thisQuery.getBirthdayEnd()));
        }
        if (StringUtils.isNotEmpty(thisQuery.getGmtCreateStart())) {
            Expression<String> endTimeExpression = criteriaBuilder.function("DATE_FORMAT", String.class,
                    root.get("gmtCreate"), criteriaBuilder.literal("%Y-%m-%d"));
            predicates.add(criteriaBuilder.greaterThanOrEqualTo(endTimeExpression, thisQuery.getGmtCreateStart()));
        }
        if (StringUtils.isNotEmpty(thisQuery.getGmtCreateEnd())) {
            Expression<String> endTimeExpression = criteriaBuilder.function("DATE_FORMAT", String.class,
                    root.get("gmtCreate"), criteriaBuilder.literal("%Y-%m-%d"));
            predicates.add(criteriaBuilder.lessThanOrEqualTo(endTimeExpression, thisQuery.getGmtCreateEnd()));
        }
        return predicates;
    }

    public Pager<UserVo> findVoByPage(UserQuery query){
        Pager<User> pager = this.findByPage(query);
        Pager<UserVo> newPager = new Pager<>(pager);
        List<User> dataList = pager.getDataList();
        List<UserVo> newDataList = new ArrayList<>();
        newPager.setDataList(newDataList);
        for (User user : dataList) {
            UserVo userVo = BeanUtils.beanCopy(user, UserVo.class);
            List<Long> roleIds = user.getRoleList().stream().map(data -> data.getId()).collect(Collectors.toList());
            userVo.setRoleIds(roleIds);
            newDataList.add(userVo);

            //获取员工
            EmployeeQuery employeeQuery = new EmployeeQuery();
            employeeQuery.setUserId(user.getId());
            Employee employee = employeeService.findOne(employeeQuery);
            userVo.setEmployee(employee);


        }

        return newPager;
    }


    public User findByTypeAndMobile(Integer type, String areaCode, String mobile) {
        UserQuery userQuery = new UserQuery();
        userQuery.setType(type);
        userQuery.setAreaCode(areaCode);
        userQuery.setMobile(mobile);
        return this.findOne(userQuery);
    }

    public User findByTypeAndEmail(Integer type, String email) {
        UserQuery userQuery = new UserQuery();
        userQuery.setType(type);
        userQuery.setEmail(email);
        return this.findOne(userQuery);

    }

    public User findByTypeAndMobile(Integer type, String mobile) {
        UserQuery userQuery = new UserQuery();
        userQuery.setType(type);
        userQuery.setMobile(mobile);
        return this.findOne(userQuery);
    }

    public User findByTypeAndAccountNo(Integer type, String accountNo) {
        UserQuery userQuery = new UserQuery();
        userQuery.setType(type);
        userQuery.setAccountNo(accountNo);
        return this.findOne(userQuery);

    }

    public boolean checkUserUniqueByAccountNo(Integer type,String accountNo){
        User user = this.findByTypeAndAccountNo(type, accountNo);
        return user == null;
    }

    public boolean checkUserUniqueByMobile(Integer type,String mobile){
        User user = this.findByTypeAndMobile(type, "86",mobile);
        return user == null;
    }

    @Transactional
    public User saveUser(User user) {
        //密码加密
        String newPwd = MD5Utils.MD5EncodeForPwd(UserConstants.PWD_KEY, user.getPwd());
        user.setPwd(newPwd);

        if(StringUtils.isEmpty(user.getNickname())){
            user.setNickname(user.getAccountNo());
        }
        User savedUser = this.save(user);

        if (user.getType() == UserTypeEnum.MEMBER.getCode()) {
            //account
//            UserAccount userAccount = new UserAccount();
//            userAccount.setEnable(YesNoEnum.YES.getCode());
//            userAccount.setUserId(savedUser.getId());
//            userAccount.setAmount(0L);
//            userAccount.setWithdrawAmount(0L);
//            userAccountService.save(userAccount);

        }
        return savedUser;
    }

    public User register(UserRegisterParam userRegisterParam) {
        Integer registerType = userRegisterParam.getRegisterType();
        Integer userType = userRegisterParam.getUserType();
        String areaCode = userRegisterParam.getAreaCode();
        String mobile = userRegisterParam.getMobile();
        String email = userRegisterParam.getEmail();
        String checkCode = userRegisterParam.getCheckCode();
        String pwd = userRegisterParam.getPwd();
        //手机号码是否已被注册
        String accountNo = areaCode + mobile;

        User dbUser = null;
        if (registerType == RegisterTypeEnum.MOBILE.getCode()) {
            dbUser = this.findByTypeAndMobile(userType, areaCode, mobile);
        } else {
            dbUser = this.findByTypeAndEmail(userType, email);
            accountNo = email;
        }
        if (dbUser != null) {
            throw new BizException("手机号码已被注册");
        }

        //短信验证码是否正确
        String checkCodeKey = StringUtils.join(new Object[]{CacheConstants.USER_REGISTER_SMS, userType, accountNo}, "_");
//        String checkCodeKey = this.getCacheKey(CacheConstants.USER_REGISTER_SMS, userType);

        String cacheSmsCode = stringRedisTemplate.opsForValue().get(checkCodeKey);
        if (StringUtils.isEmpty(cacheSmsCode)) {
            throw new BizException("验证码已失效，请重新获取");
        }

        if (!StringUtils.equals(cacheSmsCode, checkCode)) {
            throw new BizException("验证码错误，请重新输入");
        }

        //密码加密
        String newPwd = MD5Utils.MD5EncodeForPwd(UserConstants.PWD_KEY, pwd.toUpperCase());

        //保存
        User user = new User();
        if(StringUtils.isNotEmpty(userRegisterParam.getAccountNo())){
            accountNo = userRegisterParam.getAccountNo();
        }
        user.setAccountNo(accountNo);  //默认账号，可以修改，必须保证唯一性
        user.setType(userType);
        user.setAreaCode(areaCode);
        user.setMobile(mobile);
        user.setEmail(email);
        user.setNickname(accountNo);
        user.setPwd(newPwd);
        user.setGender(UserGenderEnum.MALE.getCode());
        user.setStatus(UserStatusEnum.PASS.getCode());
        user.setUserName(accountNo);
//        user.setAvatar(+mobile);  //默认图片 TODO

        return this.saveUser(user);
    }

    public UserVo getLoginUser(HttpServletRequest request) {
        String token = request.getHeader(SystemConstant.TOKEN);
        if (StringUtils.isEmpty(token)) {
            return null;
        }
        String tokenCacheKey = getUserTokenKey(token);
//        String tokenCacheKey = StringUtils.join(new Object[]{CacheConstants.USER_TOKEN, token}, "_");
        Object cacheUser = redisTemplate.opsForValue().get(tokenCacheKey);
        if (cacheUser != null) {
            return (UserVo) cacheUser;
        } else {
            return null;
        }
    }

    public int updateStatus(Long id,Integer status){
        return userDao.updateStatus(id, status);
    }
    public int updateAllStatus(List<Long> ids,Integer status){
        return userDao.updateAllStatus(ids, status);
    }


    public void updatePwd(Long userId, String oldPwd, String newPwd) {
        User user = this.findById(userId);
        //密码加密
        String encodePwd = MD5Utils.MD5EncodeForPwd(UserConstants.PWD_KEY, oldPwd.toUpperCase());
        if (!StringUtils.equalsIgnoreCase(encodePwd, user.getPwd())) {
            throw new BizException("原始密码错误，请重新输入");
        }
        //密码加密
        String encodeNewPwd = MD5Utils.MD5EncodeForPwd(UserConstants.PWD_KEY, newPwd.toUpperCase());

        //修改
        user.setPwd(encodeNewPwd);
        this.save(user);
    }

    public String resetPwd(List<Long> userIds) {
        String pwd = RandomStringUtils.random(8, true, true);
        String md5Pwd = MD5Utils.MD5Encode(pwd);
        //密码加密
        String encodePwd = MD5Utils.MD5EncodeForPwd(UserConstants.PWD_KEY, md5Pwd);
        this.userDao.updateAllPwd(userIds,encodePwd);
        return pwd;
    }

    public void updateForgetLoginPwdBySmsCode(Integer userType, String areaCode, String mobile, String smsCode, String newPwd) {
        User user = this.findByTypeAndMobile(userType, areaCode, mobile);
        if (user == null) {
            throw new BizException("该用户没有注册");
        }
        //短信验证码是否正确
        String smsCodeKey = StringUtils.join(new Object[]{CacheConstants.USER_FORGET_PWD_SMS, userType, areaCode + mobile}, "_");
//        String smsCodeKey = this.getCacheKey(CacheConstants.USER_FORGET_PWD_SMS,userType,areaCode+mobile);

        String cacheSmsCode = stringRedisTemplate.opsForValue().get(smsCodeKey);
        if (StringUtils.isEmpty(cacheSmsCode)) {
            throw new BizException("验证码已失效，请重新获取");
        }

        if (!StringUtils.equals(cacheSmsCode, smsCode)) {
            throw new BizException("验证码错误，请重新输入");
        }
        //密码加密
        String encodePwd = MD5Utils.MD5EncodeForPwd(UserConstants.PWD_KEY, newPwd);

        //修改
        user.setPwd(encodePwd);
        this.save(user);
    }

    public void updateForgetLoginPwdByEmailCode(Integer userType, String email, String smsCode, String newPwd) {
        User user = this.findByTypeAndEmail(userType, email);
        if (user == null) {
            throw new BizException("该用户没有注册");
        }
        //短信验证码是否正确
        String smsCodeKey = StringUtils.join(new Object[]{CacheConstants.USER_FORGET_PWD_SMS, userType, email}, "_");

        String cacheSmsCode = stringRedisTemplate.opsForValue().get(smsCodeKey);
        if (StringUtils.isEmpty(cacheSmsCode)) {
            throw new BizException("验证码已失效，请重新获取");
        }

        if (!StringUtils.equals(cacheSmsCode, smsCode)) {
            throw new BizException("验证码错误，请重新输入");
        }
        //密码加密
        String encodePwd = MD5Utils.MD5EncodeForPwd(UserConstants.PWD_KEY, newPwd);

        //修改
        user.setPwd(encodePwd);
        this.save(user);
    }

    public void updateMobile(Long userId, String areaCode, String mobile, String smsCode) {
        User user = this.findById(userId);
        if (user == null) {
            throw new BizException("该用户没有注册");
        }
        //短信验证码是否正确
        String smsCodeKey = StringUtils.join(new Object[]{CacheConstants.USER_CHANGE_MOBILE_SMS, user.getType(), areaCode + mobile}, "_");

        String cacheSmsCode = stringRedisTemplate.opsForValue().get(smsCodeKey);
        if (StringUtils.isEmpty(cacheSmsCode)) {
            throw new BizException("验证码已失效，请重新获取");
        }

        if (!StringUtils.equals(cacheSmsCode, smsCode)) {
            throw new BizException("验证码错误，请重新输入");
        }

        //修改
        user.setMobile(mobile);
        this.save(user);
    }


    public void updateEmail(Long userId, String email, String smsCode) {
        User user = this.findById(userId);
        if (user == null) {
            throw new BizException("该用户没有注册");
        }
        //短信验证码是否正确
        String smsCodeKey = StringUtils.join(new Object[]{CacheConstants.USER_CHANGE_MOBILE_SMS, user.getType(), email}, "_");

        String cacheSmsCode = stringRedisTemplate.opsForValue().get(smsCodeKey);
        if (StringUtils.isEmpty(cacheSmsCode)) {
            throw new BizException("验证码已失效，请重新获取");
        }

        if (!StringUtils.equals(cacheSmsCode, smsCode)) {
            throw new BizException("验证码错误，请重新输入");
        }

        //修改
        user.setEmail(email);
        this.save(user);
    }

    private String getUserTokenKey(Object key){
        String appEnv = systemConfigComponent.getAppEnv();
        String userIdCacheKey = StringUtils.join(new Object[]{CacheConstants.USER_TOKEN, appEnv,key}, "_");
        return userIdCacheKey;
    }
    private String getCacheKey(String prefix,Object [] args){
        String appEnv = systemConfigComponent.getAppEnv();
        String userIdCacheKey = StringUtils.join(new Object[]{prefix, appEnv,args}, "_");
        return userIdCacheKey;
    }


    public String resetLoginToken(User user) {
        //剔除老的 token user 对象
        String userIdCacheKey = getUserTokenKey(user.getId());
        String oldToken = stringRedisTemplate.opsForValue().get(userIdCacheKey);
        String oldTokenCacheKey = this.getUserTokenKey(oldToken);
//        String oldTokenCacheKey = StringUtils.join(new Object[]{CacheConstants.USER_TOKEN, oldToken}, "_");
        redisTemplate.delete(oldTokenCacheKey);


        //set new token for user 对象
        String newToken = UUID.randomUUID().toString();
        stringRedisTemplate.opsForValue().set(userIdCacheKey, newToken);
//        String oldToken = stringRedisTemplate.opsForValue().get(userIdCacheKey);
//        String tokenCacheKey = StringUtils.join(new Object[]{CacheConstants.USER_TOKEN, newToken}, "_");
        String tokenCacheKey = this.getUserTokenKey(newToken);
        UserVo userVo = BeanUtils.beanCopy(user, UserVo.class);
        List<Long> roleIds = user.getRoleList().stream().map(data -> data.getId()).collect(Collectors.toList());
        userVo.setRoleIds(roleIds);
        redisTemplate.opsForValue().set(tokenCacheKey, userVo);
        return newToken;

    }


    public void clearLoginUser(HttpServletRequest request) {
        String token = request.getHeader(SystemConstant.TOKEN);
        if (StringUtils.isEmpty(token)) {
            return;
        }
//        String tokenCacheKey = StringUtils.join(new Object[]{CacheConstants.USER_TOKEN, token}, "_");
        String tokenCacheKey = this.getUserTokenKey(token);
        redisTemplate.delete(tokenCacheKey);
    }

    public User loginByPwd(Integer userType, String accountNo, String pwd) {
        User user = this.findByTypeAndAccountNo(userType, accountNo);
        if (user == null) {
            throw new BizException("账号不存在");
        }
        String newPwd = MD5Utils.MD5EncodeForPwd(UserConstants.PWD_KEY, pwd);
        if (!user.getPwd().equals(newPwd)) {
            throw new BizException("密码不正确");
        }
        return user;
    }

    public User loginByPwd(Integer userType, String accountNo, String pwd, String imgCode, String deviceId) {
        //图形验证码是否正确
        String checkCodeKey = StringUtils.join(new Object[]{CacheConstants.USER_LOGIN_SMS, userType, deviceId}, "_");
        String cacheCheckCode = stringRedisTemplate.opsForValue().get(checkCodeKey);
        if (StringUtils.isEmpty(cacheCheckCode)) {
            throw new BizException("验证码已失效，请重新获取");
        }

        if (!StringUtils.equals(cacheCheckCode, imgCode)) {
            throw new BizException("验证码错误，请重新输入");
        }
        User user = this.findByTypeAndAccountNo(userType, accountNo);
        if (user == null) {
            throw new BizException("账号不存在");
        }
        String newPwd = MD5Utils.MD5EncodeForPwd(UserConstants.PWD_KEY, pwd.toUpperCase());
        if (!user.getPwd().equalsIgnoreCase(newPwd)) {
            throw new BizException("密码不正确");
        }
        return user;
    }



    public User loginBySmsCode(Integer userType, String areaCode, String mobile, String smsCode) {
        User dbUser = this.findByTypeAndMobile(userType, areaCode, mobile);
        if (dbUser == null) {
            throw new BizException("用户不存在");
        }

        //短信验证码是否正确
        String checkCodeKey = StringUtils.join(new Object[]{CacheConstants.USER_LOGIN_SMS, userType, areaCode + mobile}, "_");

        String cacheCheckCode = stringRedisTemplate.opsForValue().get(checkCodeKey);
        if (StringUtils.isEmpty(cacheCheckCode)) {
            throw new BizException("验证码已失效，请重新获取");
        }

        if (!StringUtils.equals(cacheCheckCode, smsCode)) {
            throw new BizException("验证码错误，请重新输入");
        }
        return dbUser;
    }


    public User loginByEmailCode(Integer userType, String email, String emailCode) {
        User dbUser = this.findByTypeAndEmail(userType, email);
        if (dbUser == null) {
            throw new BizException("用户不存在");
        }

        //短信验证码是否正确
        String checkCodeKey = StringUtils.join(new Object[]{CacheConstants.USER_LOGIN_SMS, userType, email}, "_");

        String cacheCheckCode = stringRedisTemplate.opsForValue().get(checkCodeKey);
        if (StringUtils.isEmpty(cacheCheckCode)) {
            throw new BizException("验证码已失效，请重新获取");
        }

        if (!StringUtils.equals(cacheCheckCode, emailCode)) {
            throw new BizException("验证码错误，请重新输入");
        }
        return dbUser;
    }

    public User loginByWechat(Integer userType, String openId, String areaCode, String mobile, String smsCode, String avatar) {
        return null;
    }




}
